# -*- coding: utf-8 -*-

import re
import sys
import time
import datetime
import json
import math
import random
from logging import Log
from timing_clock import TimingClock
from alarm_clock import AlarmClock
from get_timestamp import *

# root_path = "../Template/"
root_path = "../../../../Plan/multitasking-work-system/HomeWork/"
root_file = "root.json"

root_task = {}
parameters = []
event_log = None
ver = "0.12.16"


class __MyStr__():
    def __init__(self, s) -> None:
        self.buff = ''+s

    def __str__(self):
        return self.buff.__str__()

    def __len__(self):
        return self.buff.__len__()

    def __add__(self, s):
        return __MyStr__(self.buff.__add__(s))

    def get_real_width(self):
        widths = [(126, 1), (159, 0), (687, 1), (710, 0), (711, 1), (727, 0), (733, 1), (879, 0),
                  (1154, 1), (1161, 0), (4347, 1), (4447, 2), (7467, 1), (7521, 0), (8369, 1), (8426, 0), (9000, 1), (9002, 2),
                  (11021, 1), (12350, 2), (12351, 1), (12438, 2), (12442, 0), (19893, 2), (19967, 1), (55203, 2), (63743, 1), (64106, 2),
                  (65039, 1), (65059, 0), (65131, 2), (65279, 1), (65376, 2), (65500, 1), (65510, 2), (120831, 1), (262141, 2), (1114109, 1),
                  (0xffffffffffffffffffffffffffffffff, 1)]
        length = 0
        for c in self.buff:
            o = ord(c)
            if o == 0xe or o == 0xf:
                continue
            for num, wid in widths:
                if o <= num:
                    length += wid
                    break
        return length

    def __format__(self, __format_spec: str) -> str:
        # *<4  *>5  *^5
        # *<4s *>5s *^5s
        # 4  4s
        format_spec = __format_spec
        format_len = int(re.findall('.*?[<^>]?([\d+]+)s?', format_spec)[0])
        format_len -= self.get_real_width()-len(self.buff)
        format_spec = re.sub('.*?[<^>]?([\d+]+)s?', str(format_len), format_spec)

        return self.buff.__format__(format_spec)


def printHelp():
    cmds = [
            ("v", "输出软件版本号"),
            ("h/help", "输出帮助信息"),
            ("r/reload", "重新加载任务树（修改了JSON文件后使用）"),
            ("tree", "输出整个任务树的 JSON"),
            ("ts", "[tree simple] 输出简化的任务树"),
            ("ts next", "输出简化的任务树（跳过已经结束的事项）"),
            ("n/next", "输出所有开始了但未结束的事项"),
            ("add <N> <Time> <Add_Time>", "对事件直接添加时间（<N>为next列出来的事项序号，<Time>为分钟数），"),
            ("", "<Add_Time>为添加时间，格式为“hh:mm”，默认时间为当天，不指定时为当前时间）"),
            ("t/time <N> <Time>", "任务计时（<N>为next列出来的事项序号，<Time>为倒计时分钟数，不指定时为正计时）"),
            ("a/alarm <Time> <y/f>", "闹钟计时（<Time>为倒计时分钟数，<y/f>为网易云/Foobar，不指定为默认铃声）"),
            ("c/count <N>", "统计任务用时（<N>为next列出来的事项序号）"),
            ("c/count <Item_ID>", "统计任务用时（<Item_ID>为事项名称+开始时间组成的事项ID）"),
            ("st <N>", "统计当天的任务用时（<N>为next列出来的事项序号，不指定则统计当天所有任务）"),
            ("dd <N>", "过去第N天的具体情况（<N>为正整数）"),
            ("sd <N>", "统计过去N天的时间情况（<N>为正整数）"),
            ("dw <N>", "过去第N周的具体情况（<N>为非负整数，不输入则为0，0为本周）"),
            ("sw <N>", "统计过去N周的情况（<N>同上）"),
            ("sm", "统计过去一个月的时间情况（以周为单位） "),
            ("sy", "统计过去一个月的时间情况（以月为单位） "),
            ("gd/gt", "将日期/日期+时间复制到剪切板"),
            ("tou <N>", "掷骰子，随机返回一个 1-N 的点数"),
            ("q","退出系统")
        ]

    for cmd in cmds:
        if cmd[0]:
            print("{:<25s} : {}".format(cmd[0], cmd[1]))
        else:
            print("{:<25s}   {}".format(cmd[0], cmd[1]))


def loadSubTask(sub_file):
    try:
        print("Loading: " + sub_file)
        with open(root_path+sub_file, encoding='utf-8') as f:
            file_content = f.read()
            node_task = json.loads(file_content)
            if "sub_task" in node_task:
                for sub_task in node_task["sub_task"]:
                    sub_task["task_content"] = loadSubTask(sub_task["file_name"])
            return node_task
    except Exception as e:
        print(e)
        return None


def loadTask():
    global root_task

    with open(root_path+root_file, encoding='utf-8') as f:
        file_content = f.read()
        # print(file_content)
        root_task = json.loads(file_content)
        if "sub_task" in root_task:
            for sub_task in root_task["sub_task"]:
                sub_task["task_content"] = loadSubTask(sub_task["file_name"])


indent = 0

def printTaskSimple(node):
    global indent

    # 当任务/阶段已结束时，不显示
    if "next" in parameters:
        if "end_time" in node and node["end_time"] != "":
            end_time = time.mktime(time.strptime(node["end_time"], "%Y-%m-%d %H:%M:%S"))
            if time.time() > end_time:
                return

    indent_str = ""
    if indent < 1:
        indent_str = indent*"    "
    else:
        indent_str = (indent-1)*"    " + "  └─  "

    if "task_name" in node:
        print(indent_str + node["task_name"] + " [Task]")
    elif "stage_name" in node:
        print(indent_str + node["stage_name"] + " [Stage]")
    elif "step_name" in node:
        print(indent_str + node["step_name"] + " [Step]")
    elif "item_name" in node:
        print(indent_str + node["item_name"] + " [Item]")
    elif "event_name" in node:
        print(indent_str + node["event_name"] + " [LoopEvent]")

    indent = indent + 1
    if "sub_task" in node:
        for sub_task in node["sub_task"]:
            printTaskSimple(sub_task)
    elif "task_content" in node and node["task_content"] != None:
        if "sub_task" in node["task_content"]:
            for sub_step in node["task_content"]["sub_task"]:
                printTaskSimple(sub_step)
        elif "stages" in node["task_content"]:
            for sub_step in node["task_content"]["stages"]:
                printTaskSimple(sub_step)
        elif "items" in node["task_content"]:
            for sub_item in node["task_content"]["items"]:
                printTaskSimple(sub_item)
        if "cyclic_event" in node["task_content"]:
            for sub_item in node["task_content"]["cyclic_event"]:
                printTaskSimple(sub_item)
    elif "steps" in node:
        for sub_step in node["steps"]:
            printTaskSimple(sub_step)
    # elif "items" in node:
    #     for sub_item in node["items"]:
    #         printTaskSimple(sub_item)
    indent = indent - 1


def printTask():
    print("-"*20, sys._getframe().f_code.co_name, "-"*20)
    content = json.dumps(root_task, ensure_ascii=False, indent=4)
    print(content)


step_list = []
event_list = []

def ergodicTaskTree(node):
    # 当任务/阶段已结束时，不再向下遍历
    if "end_time" in node and node["end_time"] != "":
        end_time = time.mktime(time.strptime(node["end_time"], "%Y-%m-%d %H:%M:%S"))
        if time.time() > end_time:
            return

    # 当任务/阶段还未开始，不再向下遍历
    if "start_time" in node:
        if node["start_time"] == "":
            return
        else:
            start_time = time.mktime(time.strptime(node["start_time"], "%Y-%m-%d %H:%M:%S"))
            if time.time() < start_time:
                return

    if "sub_task" in node:
        for sub_task in node["sub_task"]:
            ergodicTaskTree(sub_task)
    elif "task_content" in node and node["task_content"] != None:
        if "cyclic_event" in node["task_content"]:
            for sub_event in node["task_content"]["cyclic_event"]:
                if "event_name" in sub_event:
                    # 判断是否开始
                    if "start_time" not in sub_event or sub_event["start_time"] == "":
                        continue
                    else:
                        start_time = time.mktime(time.strptime(sub_event["start_time"], "%Y-%m-%d %H:%M:%S"))
                        if time.time() < start_time:
                            continue

                    # 判断是否已经结束
                    if "end_time" in sub_event and sub_event["end_time"] != "":
                        if sub_event["end_time"] != "":
                            end_time = time.mktime(time.strptime(sub_event["end_time"], "%Y-%m-%d %H:%M:%S"))
                            if time.time() > end_time:
                                continue;

                    # 判断是否已经完成
                    if "time" in sub_event:
                        if sub_event["interval"] == "daily":
                            if int(sub_event["time"])*60 <= event_log.getDailyUsedTime(sub_event["event_name"]+sub_event["start_time"]):
                                continue
                        elif sub_event["interval"] == "weekly":
                            if int(sub_event["time"])*60 <= event_log.getWeeklyUsedTime(sub_event["event_name"]+sub_event["start_time"]):
                                continue
                        elif sub_event["interval"] == "monthly":
                            if int(sub_event["time"])*60 <= event_log.getMonthlyUsedTime(sub_event["event_name"]+sub_event["start_time"]):
                                continue
                    elif "number" in sub_event:
                        if sub_event["interval"] == "daily":
                            if int(sub_event["number"]) <= event_log.getDailyUsedNum(sub_event["event_name"]+sub_event["start_time"]):
                                continue
                        elif sub_event["interval"] == "weekly":
                            if int(sub_event["number"]) <= event_log.getWeeklyUsedNum(sub_event["event_name"]+sub_event["start_time"]):
                                continue
                        elif sub_event["interval"] == "monthly":
                            if int(sub_event["number"]) <= event_log.getMonthlyUsedNum(sub_event["event_name"]+sub_event["start_time"]):
                                continue

                    # 添加到事件列表
                    event_list.append(sub_event)

        if "sub_task" in node["task_content"]:
            for sub_step in node["task_content"]["sub_task"]:
                ergodicTaskTree(sub_step)
        elif "stages" in node["task_content"]:
            for sub_step in node["task_content"]["stages"]:
                ergodicTaskTree(sub_step)
        elif "items" in node["task_content"]:
            for sub_step in node["task_content"]["items"]:
                if "item_name" in sub_step:
                    if "start_time" not in sub_step or sub_step["start_time"] == "":
                        continue
                    else:
                        start_time = time.mktime(time.strptime(sub_step["start_time"], "%Y-%m-%d %H:%M:%S"))
                        if time.time() < start_time:
                            continue
                    if "end_time" in sub_step and sub_step["end_time"] != "":
                        if sub_step["end_time"] != "":
                            end_time = time.mktime(time.strptime(sub_step["end_time"], "%Y-%m-%d %H:%M:%S"))
                            if time.time() > end_time:
                                continue
                    event_list.append(sub_step)

    elif "steps" in node:
        for sub_step in node["steps"]:
            # ergodicTaskTree(sub_step)
            if "step_name" in sub_step:
                if "start_time" not in sub_step or sub_step["start_time"] == "":
                    continue
                else:
                    start_time = time.mktime(time.strptime(sub_step["start_time"], "%Y-%m-%d %H:%M:%S"))
                    if time.time() < start_time:
                        continue
                if "end_time" in sub_step and sub_step["end_time"] != "":
                    if sub_step["end_time"] != "":
                        end_time = time.mktime(time.strptime(sub_step["end_time"], "%Y-%m-%d %H:%M:%S"))
                        if time.time() > end_time:
                            continue;
                event_list.append(sub_step)


def NCheck():
    if len(parameters) < 1:
        print("请输入开始计时的任务编号！")
        return False

    if not parameters[0].isdigit():
        print("命令参数错误，第1参数应为[n/next]的任务序号！")
        return False

    index = int(parameters[0])
    if index < 0 or index >= len(step_list):
        print("请输入[n/next]中的序号！")
        return False

    return True


def printPriorityStep():
    global step_list
    ergodicTaskTree(root_task)

    # 计算事件的实际优先级
    for event in event_list:
        if "priority" not in event:
            event["real_priority"] = 0
        # 周期事件
        elif "time" in event or "number" in event:
            event["real_priority"] = math.floor(event["priority"] * event_log.getProcess(event["interval"]))
        # 含有deadline
        elif "deadline" in event:
            deadline = time.mktime(time.strptime(event["deadline"], "%Y-%m-%d %H:%M:%S"))
            now_time = time.time()
            if now_time > deadline:
                event["real_priority"] = event["priority"] * 10
            else:
                start_time = time.mktime(time.strptime(event["start_time"], "%Y-%m-%d %H:%M:%S"))
                event["real_priority"] = math.floor(event["priority"] * ((deadline-start_time)/(deadline-now_time)))
        else:
            event["real_priority"] = event["priority"]

    # 根据优先级排序，倒序输出
    trends = sorted(event_list, key = lambda e:e["real_priority"], reverse=False)
    list_len = len(trends)
    step_list = [None] * list_len
    i = 1
    for event in trends:
        step_index = list_len - i
        print("\n" + "-"*20 + str(step_index) + "-"*20)
        if "event_name" in event:
            step_list[step_index] = event["event_name"] + event["start_time"]
            print("event_name:", event["event_name"])
        elif "item_name" in event:
            step_list[step_index] = event["item_name"] + event["start_time"]
            print("item_name :", event["item_name"])
        else:
            step_list[step_index] = event["step_name"] + event["start_time"]
            print("step_name :", event["step_name"])
        "start_time" in event and print("start_time:", event["start_time"])
        if "deadline" in event:
            print("deadline  :", event["deadline"])
            print("remaining : ", end = "")
            deadline = time.mktime(time.strptime(event["deadline"], "%Y-%m-%d %H:%M:%S"))
            now_time = time.time()
            printTime( int(deadline - now_time) )
        "interval" in event and print("interval  :", event["interval"])
        "time" in event and print("time      :", event["time"])
        "number" in event and print("number    :", event["number"])
        print("priority  :", event["real_priority"])
        i = i + 1


def printTime(t):
    if t < 0:
        print("- ", end="")
        t = -t
    days,rem = divmod(t, 60*60*24)
    hours,rem = divmod(rem, 3600)
    minutes,seconds = divmod(rem, 60)
    if days:
        print("{}d ".format(days), end="")
    if hours:
        print("{}h ".format(hours), end="")
    if minutes:
        print("{}m ".format(minutes), end="")
    print("{}s".format(seconds))


def getTimeStr(t, min_unit):
    time_str = ""
    if t < 0:
        time_str = time_str + "- "
        t = -t
    days,rem = divmod(t, 60*60*24)
    hours,rem = divmod(rem, 3600)
    minutes,seconds = divmod(rem, 60)
    if days:
        time_str = time_str + "{}d".format(days)
        if min_unit == "d": return time_str
        time_str = time_str + " {:0>2d}h".format(hours)
    elif hours:
        time_str = time_str + " {:>2d}h".format(hours)
    if min_unit == "h": return time_str
    if days or hours or minutes:
        time_str = time_str + " {:0>2d}m".format(minutes)
        if min_unit == "m": return time_str
    time_str = time_str + " {:0>2d}s".format(seconds)
    return time_str


def printCount(fetch):
    if len(fetch) <= 0:
        print("\r\nTotal times: 0\r\nTotal time spent: 0s")
        return

    spend_time = 0
    print("{:^22s}|  {:^40s} | {:^8s}".format("Time", __MyStr__("Item"), "Spend"))
    print("{:^22s}|  {:^40s} | {:^8s}".format("-"*11, __MyStr__("-"*20), "-"*4))

    for line in fetch:
        spend_time = spend_time + line[2]
        print("{:^22s}|  {:<40s} | {:>8s}".format(line[0], __MyStr__(line[1]), getTimeStr(line[2], "m")))

    print("\r\nNumber of items:", len(fetch))
    print("Total time spent: ", end="")
    printTime(spend_time)


def count(item_id):
    fetch = event_log.getItemDetails(item_id)
    printCount( fetch )


def statisticOfToday(item_id):
    fetch = []
    if item_id == -1:
        fetch = event_log.getAllItemToday()
    else:
        fetch = event_log.getItemToday(item_id)
    printCount( fetch )


def detailsOfThePastDay(n):
    fetch = event_log.getDetailsOfThePastDay(-n)
    printCount( fetch )


def statisticPastNDay(start_n, end_n):
    n = start_n - end_n
    now_day = datetime.datetime.now()
    total_used = 0
    for i in range(-start_n + 1, -end_n + 1):
        temp_day = now_day + datetime.timedelta(days=i)
        print(temp_day.strftime("%Y-%m-%d  "), end="")

        range_start, range_end = event_log.getSpecifiedRange(temp_day, "daily")
        used = event_log.getUse( range_start, range_end )
        total_used = total_used + used

        hours,rem = divmod(used, 3600)
        minutes,seconds = divmod(rem, 60)
        print("{:>2d}:{:>2d}".format(hours,minutes), end="  ")
        print("#"*hours)

    print("\r\nAverage per day: ", end="")
    printTime(int(total_used/n))


def detailsOfThePastWeek(n):
    day_of_week = datetime.datetime.now().weekday() + 1
    if n == 0:
        statisticPastNDay(n*7 + day_of_week, 0)
    else:
        statisticPastNDay(n*7 + day_of_week, (n-1)*7 + day_of_week)


def statisticPastNWeek(n):
    if not n.isdigit() or int(n) < 0:
        print("[statistic past N week] 命令参数应为非负整数！")
        return

    n = int(n)
    print("{:^12s}|{:^12s}|{:^12s}|{:^9s}".format("Start", "End", "Total", "Average"))
    print("{:^12s}|{:^12s}|{:^12s}|{:^9s}".format("-"*6, "-"*6, "-"*6, "-"*6))
    now_day = datetime.datetime.now()
    for i in range(-n, 1):
        temp_day = now_day + datetime.timedelta(days=i*7)
        range_start,range_end = event_log.getSpecifiedRange(temp_day, "weekly")
        used = event_log.getUse(range_start,range_end)
        day_num = 7
        if i == 0:
            day_num = temp_day.isoweekday()
        print("{:^12s}|{:^12s}|{:>11s} |{:>9s}".format(
            range_start[:10], range_end[:10], getTimeStr(used, "m"),
            getTimeStr(int(used/day_num), "m")))


def statisticOfPastMonth():
    pass


def statisticOfPastYear():
    pass



if __name__ == '__main__':
    # global root_path
    if len(sys.argv) == 2:
        root_path = sys.argv[1]
        print(root_path)

    loadTask()
    event_log = Log()

    while True:
        input_str = input("\r\n>> ").strip()
        input_list = input_str.split(" ")
        cmd = input_list[0]
        if len(input_list) > 1:
            parameters = input_list[1:]
        else:
            parameters.clear()

        if len(cmd) == 0:
            continue

        elif cmd == "v":
            print(ver)

        elif cmd == "h" or cmd == "help":
            printHelp()

        elif cmd == "r" or cmd == "reload":
            event_list.clear()
            step_list.clear()
            loadTask()

        if cmd == "tree":
            printTask()

        elif cmd == "ts":
            printTaskSimple(root_task)

        elif cmd == "n" or cmd == "next":
            event_list.clear()
            step_list.clear()
            printPriorityStep()

        elif cmd == "add":
            para_len = len(parameters)
            if para_len != 2 and para_len != 3:
                print("Usage: add <N> <Time> \"<Add_Time>\"")
                continue
            if NCheck():
                if not parameters[1].isdigit():
                    print("[add] 命令参数错误，第2参数为分钟数（应正整数）！")
                    continue
                index = int(parameters[0])
                minutes = int(parameters[1])
                seconds = minutes*60
                if para_len == 2:
                    event_log.append(step_list[index], seconds)
                else:
                    event_log.appendWithTime(step_list[index], seconds, parameters[2])

        elif cmd == "t" or cmd == "time":
            if NCheck():
                t = TimingClock()
                if len(parameters) >= 2:
                    if not parameters[1].isdigit():
                        print("[time] 命令参数错误，第2参数应为倒计时分钟数（正整数）！")
                        continue
                    tm = int(parameters[1])
                    if tm <= 0:
                        print("[time] 命令参数错误，第2参数应为倒计时分钟数（正整数）！")
                        continue
                    t.setCountDown(tm*60)
                index = int(parameters[0])
                print("\n" + step_list[index] + " start.")
                print("操作说明: [i:暂停  s:重启  c:取消  e:结束]")
                # 进入计时交互
                t.start()
                c = None
                while True:
                    c = input()
                    if c == 'i' or c == 's':
                        if t.is_alive():
                            t.setState(c)
                    if c == 'c' or c == 'e':
                        if t.is_alive():
                            t.setState(c)
                        break
                # 有时间戳代表此任务在暂停了大于30分钟后自动结束
                if t.pause_timestamp != "":
                    # 此时如果计时大于30分钟，则自动记录（基于停止的时间）；否则直接舍弃
                    if t.count > 1800: # 30*60
                        event_log.appendWithTime(step_list[index], t.count, t.pause_timestamp)
                    t.join()
                    continue
                # c取消；否则记录到日志
                if c == 'c':
                    t.join()
                    continue
                event_log.append(step_list[index], t.count)
                t.join()

        elif cmd == "a" or cmd == "alarm":
            if len(parameters) < 1 or len(parameters) > 2:
                print("[alarm] 命令参数个数有误！")
            elif not parameters[0].isdigit():
                print("[alarm] 命令参数错误，第1参数应为倒计时分钟数（正整数）！")
            else:
                t = AlarmClock()
                t.setCountDown(int(parameters[0]) * 60)
                if len(parameters) >= 2:
                    t.setWakeUpMode(parameters[1])
                # 进入计时交互
                print("操作说明: [i:暂停  s:重启  c:取消  e:结束]")
                t.start()
                c = None
                while True:
                    c = input()
                    if c == 'i' or c == 's':
                        if t.is_alive():
                            t.setState(c)
                    if c == 'c' or c == 'e':
                        if t.is_alive():
                            t.setState(c)
                        break
                    if not t.is_alive():
                        break
                t.join()

        elif cmd == "c" or cmd == "count":
            if len(parameters) > 1:
                print("[count] 命令参数个数有误！")
            elif parameters[0].isdigit():
                if NCheck():
                    count(step_list[int(parameters[0])])
            else:
                count(parameters[0])

        elif cmd == "st":
            if len(parameters) > 1:
                print("[statistic today] 命令参数个数有误！")
            elif len(parameters) == 1:
                if NCheck():
                    statisticOfToday(step_list[int(parameters[0])])
            else:
                statisticOfToday(-1)

        elif cmd == "dd":
            if len(parameters) != 1:
                print("[details of the past day N] 命令参数个数有误！")
            else:
                if parameters[0].isdigit() and int(parameters[0]) > 0:
                    detailsOfThePastDay(int(parameters[0]))
                else:
                    print("[details of the past day N] 命令参数应为正整数！")

        elif cmd == "sd":
            if len(parameters) != 1:
                print("[statistic past N day] 命令参数个数有误！")
            else:
                if parameters[0].isdigit() and int(parameters[0]) > 0:
                    statisticPastNDay(int(parameters[0]), 0)
                else:
                    print("[statistic past N day] 命令参数应为正整数！")

        elif cmd == "dw":
            if len(parameters) == 0:
                detailsOfThePastWeek(0)
            elif len(parameters) > 1:
                print("[details of the past week N] 命令参数个数有误！")
            else:
                if parameters[0].isdigit() and int(parameters[0]) >= 0:
                    detailsOfThePastWeek(int(parameters[0]))
                else:
                    print("[details of the past week N] 命令参数应为非负整数！")

        elif cmd == "sw":
            if len(parameters) == 0:
                statisticPastNWeek("0")
            elif len(parameters) > 1:
                print("[statistic past N week] 命令参数个数有误！")
            else:
                statisticPastNWeek(parameters[0])

        elif cmd == "sm":
            statisticOfPastMonth()

        elif cmd == "sy":
            statisticOfPastYear()

        elif cmd == "gd":
            getNowDay()

        elif cmd == "gt":
            getNowTime()

        elif cmd == "tou":
            if len(parameters) != 1:
                print("[tou] 命令参数个数有误！")
            elif parameters[0].isdigit() and int(parameters[0]) >= 2:
                print( random.randint(1, int(parameters[0])) )
            else:
                print("[tou] N 是一个 >= 2 的整数")

        elif cmd == "q":
            break